<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<link rel="import" href="dom-innerHTML.html">
<script>

  /**
   * DomApi is a dom manipulation library which is compatible with both
   * Shady DOM and Shadow DOM. The general usage is
   * `Polymer.dom(node).method(arguments)` where methods and arguments
   * match native DOM where possible.
   */
  Polymer.DomApi = (function() {
    'use strict';

    var Settings = Polymer.Settings;
    var TreeApi = Polymer.TreeApi;

    var DomApi = function(node) {
      this.node = needsToWrap ? DomApi.wrap(node) : node;
    };

    // ensure nodes are wrapped if SD polyfill is present
    var needsToWrap = Settings.hasShadow && !Settings.nativeShadow;
    DomApi.wrap = window.wrap ? window.wrap : function(node) { return node; };

    DomApi.prototype = {

      flush: function() {
        Polymer.dom.flush();
      },

      /**
       * Check that the given node is a descendant of `this`,
       * ignoring ShadowDOM boundaries
       * @param {Node} node
       * @return {Boolean} true if `node` is a descendant or equal to `this`
       */
      deepContains: function(node) {
        // fast path, use shallow `contains`.
        if (this.node.contains(node)) {
          return true;
        }
        var n = node;
        var doc = node.ownerDocument;
        // walk from node to `this` or `document`
        while (n && n !== doc && n !== this.node) {
          // use logical parentnode, or native ShadowRoot host
          n = Polymer.dom(n).parentNode || n.host;
        }
        return n === this.node;
      },

      /*
        Returns a list of nodes distributed within this element. These can be
        dom children or elements distributed to children that are insertion
        points.
      */
      queryDistributedElements: function(selector) {
        var c$ = this.getEffectiveChildNodes();
        var list = [];
        for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
          if ((c.nodeType === Node.ELEMENT_NODE) &&
              DomApi.matchesSelector.call(c, selector)) {
            list.push(c);
          }
        }
        return list;
      },

      /*
        Returns a list of effective childNoes within this element. These can be
        dom child nodes or elements distributed to children that are insertion
        points.
      */
      getEffectiveChildNodes: function() {
        var list = [];
        var c$ = this.childNodes;
        for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
          if (c.localName === CONTENT) {
            var d$ = dom(c).getDistributedNodes();
            for (var j=0; j < d$.length; j++) {
              list.push(d$[j]);
            }
          } else {
            list.push(c);
          }
        }
        return list;
      },

      /**
       * Notifies callers about changes to the element's effective child nodes,
       * the same list as returned by `getEffectiveChildNodes`.
       * @param {function} callback The supplied callback is called with an
       * `info` argument which is an object that provides
       * the `target` on which the changes occurred, a list of any nodes
       * added in the `addedNodes` array, and nodes removed in the
       * `removedNodes` array.
       * @return {object} Returns a handle which is the argument to
       * `unobserveNodes`.
       */
      observeNodes: function(callback) {
        if (callback) {
          if (!this.observer) {
            this.observer = this.node.localName === CONTENT ?
              new DomApi.DistributedNodesObserver(this) :
              new DomApi.EffectiveNodesObserver(this);
          }
          return this.observer.addListener(callback);
        }
      },

      /**
       * Stops observing changes to the element's effective child nodes.
       * @param {object} handle The handle for the callback that should
       * no longer receive notifications. This handle is returned from
       * `observeNodes`.
       */
      unobserveNodes: function(handle) {
        if (this.observer) {
          this.observer.removeListener(handle);
        }
      },

      notifyObserver: function() {
        if (this.observer) {
          this.observer.notify();
        }
      },

      // NOTE: `_query` is used primarily for ShadyDOM's querySelector impl,
      // but it's also generally useful to recurse through the element tree
      // and is used by Polymer's styling system. 
      _query: function(matcher, node, halter) {
        node = node || this.node;
        var list = [];
        this._queryElements(TreeApi.Logical.getChildNodes(node), matcher, 
          halter, list);
        return list;
      },

      _queryElements: function(elements, matcher, halter, list) {
        for (var i=0, l=elements.length, c; (i<l) && (c=elements[i]); i++) {
          if (c.nodeType === Node.ELEMENT_NODE) {
            if (this._queryElement(c, matcher, halter, list)) {
              return true;
            }
          }
        }
      },

      _queryElement: function(node, matcher, halter, list) {
        var result = matcher(node);
        if (result) {
          list.push(node);
        }
        if (halter && halter(result)) {
          return result;
        }
        this._queryElements(TreeApi.Logical.getChildNodes(node), matcher, 
          halter, list);
      }

    };

    var CONTENT = DomApi.CONTENT = 'content';

    var dom = DomApi.factory = function(node) {
      node = node || document;
      if (!node.__domApi) {
        node.__domApi = new DomApi.ctor(node);
      }
      return node.__domApi;
    };

    DomApi.hasApi = function(node) {
      return Boolean(node.__domApi);
    };

    DomApi.ctor = DomApi;

    Polymer.dom = function(obj, patch) {
      if (obj instanceof Event) {
        return Polymer.EventApi.factory(obj);
      } else {
        return DomApi.factory(obj, patch);
      }
    };

    var p = Element.prototype;
    DomApi.matchesSelector = p.matches || p.matchesSelector ||
      p.mozMatchesSelector || p.msMatchesSelector ||
      p.oMatchesSelector || p.webkitMatchesSelector;

    return DomApi;

  })();

</script>
